From 9a094a36cef90a39e1193926b65e3191990a2081 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 19 Mar 2026 16:58:05 -0500 Subject: [PATCH] archive: Prevent symlink-directory collision chmod attack (#442) MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit When unpacking a tarball containing a symlink followed by a directory entry with the same path, unpack_dir previously used fs::metadata() which follows symlinks. This allowed an attacker to modify permissions on arbitrary directories outside the extraction path. The fix uses fs::symlink_metadata() to detect symlinks and refuse to treat them as valid existing directories. Document more exhaustively+consistently security caveats. Reported-by: Sergei Zimmerman Assisted-by: OpenCode (Claude claude-opus-4-5) Signed-off-by: Colin Walters Co-authored-by: Colin Walters FG: drop test-related changes Signed-off-by: Fabian Grünbichler Fixes: CVE-2026-33056 Gbp-Pq: Topic vendor Gbp-Pq: Name tar-CVE-2026-33056.patch --- vendor/tar-0.4.44/src/archive.rs | 18 +++++++++++++++--- vendor/tar-0.4.44/src/entry.rs | 7 ++++--- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/vendor/tar-0.4.44/src/archive.rs b/vendor/tar-0.4.44/src/archive.rs index 4d569c6359..459c28b653 100644 --- a/vendor/tar-0.4.44/src/archive.rs +++ b/vendor/tar-0.4.44/src/archive.rs @@ -93,9 +93,21 @@ impl Archive { /// extracting each file in turn to the location specified by the entry's /// path name. /// - /// This operation is relatively sensitive in that it will not write files - /// outside of the path specified by `dst`. Files in the archive which have - /// a '..' in their path are skipped during the unpacking process. + /// # Security + /// + /// A best-effort is made to prevent writing files outside `dst` (paths + /// containing `..` are skipped, symlinks are validated). However, there + /// have been historical bugs in this area, and more may exist. For this + /// reason, when processing untrusted archives, stronger sandboxing is + /// encouraged: e.g. the [`cap-std`] crate and/or OS-level + /// containerization/virtualization. + /// + /// If `dst` does not exist, it is created. Unpacking into an existing + /// directory merges content. This function assumes `dst` is not + /// concurrently modified by untrusted processes. Protecting against + /// TOCTOU races is out of scope for this crate. + /// + /// [`cap-std`]: https://docs.rs/cap-std/ /// /// # Examples /// diff --git a/vendor/tar-0.4.44/src/entry.rs b/vendor/tar-0.4.44/src/entry.rs index 843719f0fa..6898b0abdd 100644 --- a/vendor/tar-0.4.44/src/entry.rs +++ b/vendor/tar-0.4.44/src/entry.rs @@ -212,8 +212,9 @@ impl<'a, R: Read> Entry<'a, R> { /// also be propagated to the path `dst`. Any existing file at the location /// `dst` will be overwritten. /// - /// This function carefully avoids writing outside of `dst`. If the file has - /// a '..' in its path, this function will skip it and return false. + /// # Security + /// + /// See [`Archive::unpack`]. /// /// # Examples /// @@ -446,7 +447,7 @@ impl<'a> EntryFields<'a> { // If the directory already exists just let it slide fs::create_dir(dst).or_else(|err| { if err.kind() == ErrorKind::AlreadyExists { - let prev = fs::metadata(dst); + let prev = fs::symlink_metadata(dst); if prev.map(|m| m.is_dir()).unwrap_or(false) { return Ok(()); } -- 2.30.2